﻿using OccurrencesCore.Model;
using OccurrencesCore.Model.Enum;
using OccurrencesCore.Service;
using OccurrencesWorkflow.BaseControl.Interface;
using OccurrencesWorkflow.BaseControl.WorkflowException;
using OccurrencesWorkflow.SpecializedControl.ScheduledReturn;
using OccurrencesWorkflow.SpecializedControl.ScheduledReturn.Tasks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OccurrencesWorkflow.BaseControl.Base
{
    /// <summary>
    /// The BaseTask class describes the base behavior of a task inside a workflow
    /// </summary>
    public abstract class BaseTask
    {
        /// <summary>
        /// The database instance of the current task
        /// </summary>
        internal WorkflowTask Task { get; set; }

        /// <summary>
        /// Create a new task object with the proper data
        /// </summary>
        /// <param name="occurrence">The occurrence that the task will be created.</param>
        /// <param name="taskType">The type of the task to be created.</param>
        /// <returns></returns>
        internal static List<WorkflowTask> CreateTaskObject(Occurrence occurrence, WorkflowTaskTypeEnum taskType)
        {
            if (WorkflowTaskService.IsCompetenceApproval(taskType))
            {
                return CreateCompetenceApprovalTasks(occurrence, taskType);
            }
            else
            {
                return CreateRegularTasks(occurrence, taskType);
            }
        }

        private static List<WorkflowTask> CreateRegularTasks(Occurrence occurrence, WorkflowTaskTypeEnum taskType)
        {
            List<WorkflowTask> createdTasks = new List<WorkflowTask>();
            WorkflowTask task = CreateBasicTask(occurrence, taskType);
            //get from the task parameter
            WorkflowTaskParameterService parameterService = new WorkflowTaskParameterService();
            WorkflowTaskParameter taskParameter = parameterService.FindByWorkflowTaskType(Convert.ToInt32(taskType));
            if (taskParameter != null)
            {
                task.DueDate = DateTime.Now.AddDays(taskParameter.BusinessDays);
                task.AssignedUserProfileId = taskParameter.UserProfileExecutorId;
            }
            createdTasks.Add(task);
            return createdTasks;
        }

        private static List<WorkflowTask> CreateCompetenceApprovalTasks(Occurrence occurrence, WorkflowTaskTypeEnum taskType)
        {
            List<WorkflowTask> createdTasks = new List<WorkflowTask>();
            //get from the competence approval parameters table
            CompetenceParametersService competenceParametersService = new CompetenceParametersService();
            WorkflowTaskParameterService parameterService = new WorkflowTaskParameterService();
            WorkflowTaskParameter taskParameter = parameterService.FindByWorkflowTaskType(Convert.ToInt32(taskType));
            List<UserProfile> listUserProfile = competenceParametersService.GetRequiredCompetenceApprovals(occurrence.Id);
            foreach (UserProfile userProfile in listUserProfile)
            {
                WorkflowTask task = CreateBasicTask(occurrence, taskType);
                task.DueDate = DateTime.Now.AddDays(taskParameter.BusinessDays);
                task.AssignedUserProfileId = userProfile.Id;
            }
            return createdTasks;
        }

        private static WorkflowTask CreateBasicTask(Occurrence occurrence, WorkflowTaskTypeEnum taskType)
        {
            WorkflowTask task = new WorkflowTask();
            task.Occurrence = occurrence;
            task.OccurrenceId = occurrence.Id;
            task.TaskData = string.Empty;
            task.Title = "";
            task.WorkflowTaskStatusId = Convert.ToInt32(WorkflowTaskStatusEnum.Opened);
            task.WorkflowTaskTypeId = Convert.ToInt32(taskType);
            task.AssignedUserId = null;
            return task;
        }

        /// <summary>
        /// Constructor of the base class that populate the property that holds the database instance
        /// </summary>
        /// <param name="taskId">The id of the task on the database</param>
        public BaseTask(int taskId)
        {
            WorkflowTaskService workflowTaskService = new WorkflowTaskService();
            Task = workflowTaskService.FindByKey(taskId);
            if (Task == null)
            {
                throw new WorkflowTaskNotFoundException(taskId);
            }
        }

        /// <summary>
        /// Approve the task. The status of the task will be changed and a log entry will be created.
        /// <param name="aditionalMessage">An optional parameter to extend the log message.</param>
        /// </summary>
        internal void Approve(string aditionalMessage = "")
        {
            if (TaskCanBeChanged())
            {
                Task.WorkflowTaskStatusId = Convert.ToInt32(WorkflowTaskStatusEnum.Approved);
                SaveTask();
                LogTask(CreateBaseLogMessage(WorkflowMessages.APPROVE_TASK) + " " + aditionalMessage);
                NotifyWorkflow();
            }
            else
            {
                throw new WorkflowTaskAlreadyEditedException(Task.Id);
            }
        }

        /// <summary>
        /// Reject the task. The status of the task will be changed and a log entry will be created.
        /// <param name="aditionalMessage">An optional parameter to extend the log message.</param>
        /// </summary>
        internal void Reject(string aditionalMessage = "")
        {
            if (TaskCanBeChanged())
            {
                Task.WorkflowTaskStatusId = Convert.ToInt32(WorkflowTaskStatusEnum.Rejected);
                SaveTask();
                LogTask(CreateBaseLogMessage(WorkflowMessages.APPROVE_TASK) + " " + aditionalMessage);
                NotifyWorkflow();
            }
            else
            {
                throw new WorkflowTaskAlreadyEditedException(Task.Id);
            }
        }

        /// <summary>
        /// Complete the task. The status of the task will be changed and a log entry will be created.
        /// <param name="aditionalMessage">An optional parameter to extend the log message.</param>
        /// </summary>
        internal void Complete(string aditionalMessage = "")
        {
            if (TaskCanBeChanged())
            {
                Task.WorkflowTaskStatusId = Convert.ToInt32(WorkflowTaskStatusEnum.Completed);
                SaveTask();
                LogTask(CreateBaseLogMessage(WorkflowMessages.COMPLETE_TASK) + " " + aditionalMessage);
                NotifyWorkflow();
            }
            else
            {
                throw new WorkflowTaskAlreadyEditedException(Task.Id);
            }
        }

        /// <summary>
        /// The Postpone action means that the user has filed in some information and want to save it 
        /// without completing the task. 
        /// <param name="taskData">The data that was input for this task in a string format.</param>
        /// <param name="aditionalMessage">An optional parameter to extend the log message.</param>
        /// </summary>
        internal void Postpone(string taskData, string aditionalMessage = "")
        {
            //TODO: finish implementation
            Task.TaskData = taskData;
            SaveTask();
            LogTask(CreateBaseLogMessage(WorkflowMessages.POSTPONE_TASK) + " " + aditionalMessage);
            NotifyWorkflow();
        }

        /// <summary>
        /// The Reassign User action is used when the responsible for the task is changed to a single user.
        /// </summary>
        /// <param name="user">The user who will be the new responsible for the task.</param>
        internal void Reassign(User user)
        {
            //remove any old assignee
            Task.AssignedUserProfileId = null;
            Task.AssignedUserProfile = null;
            Task.AssignedUserId = user.Id;
            Task.AssignedUser = user;
            SaveTask();
            LogTask(CreateReassigneLogMessage(WorkflowMessages.REASIGN_TASK_USER, user.Name));
            NotifyWorkflow();
        }

        /// <summary>
        /// The Reassign User Profile action is used when the responsible for the task is changed to a user profile.
        /// </summary>
        /// <param name="userProfile">The user profile who will be the new responsible for the task.</param>
        internal void Reassign(UserProfile userProfile)
        {
            //remove any old assignee
            Task.AssignedUserId = null;
            Task.AssignedUser = null;
            Task.AssignedUserProfileId = userProfile.Id;
            Task.AssignedUserProfile = userProfile;
            SaveTask();
            LogTask(CreateReassigneLogMessage(WorkflowMessages.REASIGN_TASK_USER_PROFILE, userProfile.Name));
            NotifyWorkflow();
        }

        /// <summary>
        /// The Cancel action is used when a task is cancelled based on other task changes. It usually is a system action only. The status of the task will be changed.
        /// </summary>
        /// <param name="aditionalMessage"></param>
        internal void Cancel(string aditionalMessage = "")
        {
            Task.WorkflowTaskStatusId = Convert.ToInt32(WorkflowTaskStatusEnum.Cancelled);
            SaveTask();
            LogTask(CreateSystemLogMessage(WorkflowMessages.CANCEL_TASK) + " " + aditionalMessage, true);
            NotifyWorkflow();
        }

        /// <summary>
        /// Save the current Task on the database
        /// </summary>
        private void SaveTask()
        {
            WorkflowTaskService service = new WorkflowTaskService();
            Task = service.Update(Task);
        }

        /// <summary>
        /// Log a message on the task log 
        /// </summary>
        /// <param name="message">The message to be logged</param>
        /// <param name="displayOnlyToAdmin">Indicates if the log item should be displayed for all users or only to admins. The default is to display for all users.</param>
        private void LogTask(string message, bool displayOnlyToAdmin = false)
        {
            WorkflowTaskLogService service = new WorkflowTaskLogService();
            WorkflowTaskLog taskLog = new WorkflowTaskLog()
            {
                Message = message,
                WorkflowTaskId = Task.Id,
                AdminOnly = displayOnlyToAdmin
            };
            service.Create(taskLog);
        }

        /// <summary>
        /// Indicates if the task can be changed at its current status.
        /// </summary>
        /// <returns>True if the task can be changed, false otherwise.</returns>
        private bool TaskCanBeChanged()
        {
            if ((WorkflowTaskStatusEnum)Task.WorkflowTaskStatusId == WorkflowTaskStatusEnum.Opened)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Notify the workflow of any changes on the task so the proper actions can be taken
        /// </summary>
        private void NotifyWorkflow()
        {
            IWorkflowControl workflowControl = WorkflowControl.GetWorkflow(Task.Occurrence);
            if (workflowControl != null)
            {
                workflowControl.NotifyTaskChanged(Task);
            }
        }

        /// <summary>
        /// Creates a base log message, with the type of the task, the user who modified and the modified date.
        /// </summary>
        /// <param name="baseMessage">The base message that will be used.</param>
        /// <returns>The formated message with the properties</returns>
        private string CreateBaseLogMessage(string baseMessage)
        {
            return string.Format(baseMessage, Task.WorkflowTaskType.Description, Task.ModifiedBy.Name, Task.ModifiedAt.ToString("dd/MM/yyyy hh:mm:ss"));
        }

        /// <summary>
        /// Creates a base log message, with the type of the task and the modified date.
        /// </summary>
        /// <param name="baseMessage">The base message that will be used.</param>
        /// <returns>The formated message with the properties</returns>
        private string CreateSystemLogMessage(string baseMessage)
        {
            return string.Format(baseMessage, Task.WorkflowTaskType.Description, Task.ModifiedAt.ToString("dd/MM/yyyy hh:mm:ss"));
        }

        /// <summary>
        /// Creates a base log message, with the type of the task, the user who modified, the modified date and the new assignee information.
        /// </summary>
        /// <param name="baseMessage">The base message that will be used.</param>
        /// <param name="newAssigneInformation">The information about the new assignee.</param>
        /// <returns>The formated message with the properties</returns>
        private string CreateReassigneLogMessage(string baseMessage, string newAssigneInformation)
        {
            return string.Format(baseMessage, Task.WorkflowTaskType.Description, Task.ModifiedBy.Name, Task.ModifiedAt.ToString("dd/MM/yyyy hh:mm:ss"), newAssigneInformation);
        }

    }
}
